/*
 * Copyright (C) 2013 Manoel Afonso Filho
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package agimagem.concrete;

import agimagem.fitness.Fitness;
import agimagem.populacao.Individuo;
import agimagem.populacao.Populacao;
import agimagem.strategy.Inicializacao;
import agimagem.util.AGChave;
import agimagem.util.Util;

/**
 * Implementação do processo de geração da população inicial do AG.
 * Este processo utiliza o algoritmo descrito no paper
 * <i>Secret Key Encryption Algorithm Using Genetic Algorithm (Ankita Argawal)</i>.
 * <p>
 * O conjunto de bytes fornecidos (plaintext) são dividivos em blocos de 64 bits
 * (8 bytes). Os parâmetros <code>a</code> e <code>b</code>, escolhidos aleatoriamente,
 * equivalem às posições dos bytes a serem trocados de posição. Após a troca,
 * o parâmetro <code>k</code>, também aleatório, é usado para inverter o valor
 * de um byte (<code>inverso = 255 - byte</code>). O valor de <code>k</code>
 * especifica a posição do byte que terá seu valor invertido. Após isso, os blocos
 * são unidos, formando um indivíduo derivado do plaintext original. Para reverter
 * o processo, basta aplicar as opereções acima na ordem inversa.
 * </p>
 * <p>
 * Caso na divisão do plaintext em blocos de 64 bits faltarem bits no último
 * bloco, serão adicionados bites com valor 0. Dessa forma, o parâmetro
 * {@link AGChave#limiteDerivacao} especifica o tamanho original do plaintext,
 * isto é, sem a adição dos bits com valor 0 no final.
 * </p>
 * @author Manoel Afonso Filho
 */
public class ImgInicial implements Inicializacao {

    /**
     * Gera a população inicial derivando a imagem original.
     * @param img Bytes da imagem original.
     * @return População inicial.
     */
    @Override
    public Populacao criarPopulacao(byte[] img) {
        Populacao pop = new Populacao();
        // Guarda o limite dos bytes relevantes
        AGChave.limiteDerivacao = img.length;

        for (int i = 0; i < Inicializacao.numIndividuos; i++) {
            // Bytes da imagem inicial divididos em N vetores de 8 bytes cada.
            byte[][] imagemInicial = Util.dividirArray(img, 8);
            // Parâmetros da derivação
            //Sorteia as chaves são aleatórias.
            int a = Util.randInt(8);
            int b = Util.randInt(8);
            int k = Util.randInt(8);

            Individuo ind = new Individuo(gerarIndividuo(imagemInicial, a,  b, k));
            // Guarda os parâmetros usados para derivá-lo
            ind.getChaveDaGeracaoAtual().setDerivacao(a, b, k);
            ind.setFitness(Fitness.calcular(ind));
            // Adiciona o indivíduo à população
            pop.guardarIndividuo(ind);
        }

        return pop;
    }

    /**
     * Gera um indivíduo baseado na {@code imagemInicial}.
     * @param imagemInicial Plaintext.
     * @param a Parâmetro {@code a}.
     * @param b Parâmetro {@code b}.
     * @param k Parâmetro {@code k}.
     * @return Indivíduo derivado do plaintext.
     */
    private byte[] gerarIndividuo(byte[][] imagemInicial, int a, int b, int k){
        // Crossover e mutação
        byte[][] crossover = this.crossover(imagemInicial, a, b);
        byte[][] mutacao = this.mutacao(crossover, k);
        // Imagem criptografada
        byte[] imagemFinal = Util.concatenarArrays(mutacao);
        return imagemFinal;
    }

    /**
     * Recupera o indivíduo. A chave passada deve ser a mesma usada no
     * {@link ImgInicial#gerarIndividuo(byte[][], agimagem.util.Chave) embaralhamento}.
     * @param imagem Ciphertext.
     * @param a Parâmetro {@code a}.
     * @param b Parâmetro {@code b}.
     * @param k Parâmetro {@code k}.
     * @param limite Limite de derivação.
     * @return Plaintext.
     */
    public byte[] recuperarIndividuo(byte[][] imagem, int a, int b, int k, int limite){
        byte[][] mutacao = this.mutacao(imagem, k);
        byte[][] crossover = this.crossover(mutacao, a, b);
        byte[] imagemTemp = Util.concatenarArrays(crossover);

        // Só copia os bytes relevantes. Se o total de posições de imagemTemp
        // for maior que limite, os valores que estão nessas posições extras são
        // apenas bytes com valor 0 e não devem ser considerados.
        byte[] imagemFinal = new byte[limite];
        System.arraycopy(imagemTemp, 0, imagemFinal, 0, imagemFinal.length);
        return imagemFinal;
    }

    /**
     * Método utilitário para a derivação reversa.
     * @param imagem Ciphertext.
     * @param chave Chave com os parâmetros da derivação.
     * @return Plaintext.
     */
    public byte[] recuperarIndividuo(byte[][] imagem, AGChave chave){
        return recuperarIndividuo(imagem, chave.getA(), chave.getB(), chave.getK(), AGChave.limiteDerivacao);
    }

    /**
     * Crossover do paper.
     * @param imagemInicial
     * @param a Parâmetro {@code a}.
     * @param b Parâmetro {@code b}.
     * @return
     */
    public byte[][] crossover(byte[][] imagemInicial, int a, int b) {
        if(a == b){
            return imagemInicial;
        }

        for (int i = 0; i < imagemInicial.length; i++) {
            byte[] tmp = imagemInicial[i];

            byte aux = tmp[a];
            tmp[a] = tmp[b];
            tmp[b] = aux;

            imagemInicial[i] = tmp;
        }
        return imagemInicial;
    }

    /**
     * Mutação do paper.
     * @param imagemInicial
     * @param k Parâmetro {@code k}.
     * @return
     */
    public byte[][] mutacao(byte[][] imagemInicial, int k) {
        for (int i = 0; i < imagemInicial.length; i++) {
            byte[] tmp = imagemInicial[i];

            tmp[k] = (byte) (255 - tmp[k]);

            imagemInicial[i] = tmp;
        }
        return imagemInicial;
    }

    @Override
    public byte[] reverso(byte[] ciphertext, AGChave chave) {
        if(chave.getA() >= 0 && chave.getB() >= 0 && chave.getK() >= 0){
            ciphertext = recuperarIndividuo(Util.dividirArray(ciphertext, 8), chave);
        }
        return ciphertext;
    }

}
